package com.duniptech.soa.simulation;

import java.util.ArrayList;

import org.w3c.dom.Element;

import com.duniptech.soa.modeling.RTConnection;
import com.duniptech.soa.service.RTSimulationServiceInterface;
import com.duniptech.soa.util.Util;


public class RTSimulator extends Simulator implements Runnable {
	protected Thread myThread;
	protected double startTime;
	protected boolean externalTransition;
	protected boolean internalTransition;
	protected RTSimulatorTimer timer;
	protected ArrayList<RTConnection> eoc;

	public RTSimulator(Element xmlAtomic) throws Exception {
		super(xmlAtomic);
		myThread = new Thread(this);
		externalTransition = false;
		internalTransition = false;
		eoc = new ArrayList<RTConnection>();
	}
	
	public void initialize() {
		model.initialize();
	}

	public void addConnection(String simulatorFrom, String portFrom, String simulatorTo, String endPointServiceTo, String portTo) throws Exception {
		RTConnection c = new RTConnection(simulatorFrom, portFrom, simulatorTo, endPointServiceTo, portTo);
		eoc.add(c);
	}

	public void receive(String simulatorFrom, String portFrom, String simulatorTo, String portTo, String[] xmlValues) throws Exception {
		super.receive(simulatorFrom, portFrom, simulatorTo, portTo, xmlValues);
		notifyExternalTransition();
	}
	
	public void propagateOutput() {
		try {
		for(RTConnection c : eoc) {
			// Get the output:
			String[] output = super.getOutput(c.getPortFrom());
			RTSimulationServiceInterface serviceTo = c.getServiceTo();
			serviceTo.receive(c.getSimulatorTo(), c.getSimulatorFrom(), c.getPortFrom(), c.getSimulatorTo(), c.getPortTo(), output);
		}
		}
		catch(Exception ee) {
			System.out.println("RTSimulator::propagateOutput: " + ee.getLocalizedMessage());
		}
	}
	
	public void simulate() {
		if(Util.debug) System.out.println("RTSimulator::simulate: Starting thread.");
		// initialize();
		myThread.start();
		if(Util.debug) System.out.println("RTSimulator::simulate: Thread started.");
	}
	
	public void exit() {
		if(myThread!=null) myThread.interrupt();
	}
	
	///// THREAD
	
	public synchronized void waitForNextTransition() {
		while(!externalTransition && !internalTransition) {
			try {
				wait();
			}
			catch(Exception ee) {
				System.out.println("RTSimulator::waitForNextTransition: " + ee.getLocalizedMessage());
			}
		}
	}
	
	public synchronized void notifyExternalTransition() {
		externalTransition = true;
		if(Util.debug) System.out.println("RTSimulator::notifyExternalTransition: " + model.getName() + " -> notifying external transition.");
		notify();
		if(Util.debug) System.out.println("RTSimulator::notifyExternalTransition: " + model.getName() + " -> external transition notified.");
	}

	public synchronized void notifyInternalTransition() {
		internalTransition = true;
		if(Util.debug) System.out.println("RTSimulator::notifyInternalTransition: " + model.getName() + " -> notifying internal transition.");
		notify();
		if(Util.debug) System.out.println("RTSimulator::notifyInternalTransition: " + model.getName() + " -> internal transition notified.");
	}
	
	public void run() {
		startTime = System.currentTimeMillis()/1000;
		tL = startTime;
		tN = tL + model.ta();
		if(Util.debug) System.out.println(model.getName() + " -> NextInternalTransitionIn = " + (tN - tL));		
		long counter = 1;
		while(true) {
			// Waiting internal or external transition
			if(Util.debug) System.out.println("RTSimulator::run: " + model.getName() + " waiting for the next transition.");
			while(System.currentTimeMillis() < 1000*tN - 10) {
				internalTransition = false;
				long timeToSleep = (long)(1000*tN - System.currentTimeMillis());
				if(tN < Double.POSITIVE_INFINITY) {
					timer = new RTSimulatorTimer(this, timeToSleep);
				}
				waitForNextTransition();
				if(externalTransition) break;
			}
			
			if(Util.debug) System.out.println(model.getName() + " -> Transition, tN - Time = " + (1000*tN - System.currentTimeMillis()) + ", internalTransition = " + internalTransition  + ", externalTransition = " + externalTransition);
			if(System.currentTimeMillis() >= 1000*tN - 10) internalTransition = true;
			
			// Internal transition
			if(internalTransition) {
				if(Util.debug) System.out.println(model.getClass().getName() + " -> Internal transition (" + (tN-startTime) + ").");
				super.lambda(tN);
				propagateOutput();
				super.deltfcn(tN);
			}
			else if(externalTransition) { // External transition
				double externalEventTime = System.currentTimeMillis()/1000;
				if(externalEventTime > tN) externalEventTime = tN;
				if(Util.debug) System.out.println(model.getName() + " -> External transition (" + (externalEventTime-startTime) + ").");
				super.deltfcn(externalEventTime);
			}
			
			// Stop the timer and prepare the new iteration
			if(timer!=null) timer.interrupt();
			internalTransition = false;
			externalTransition = false;
			tL = System.currentTimeMillis()/1000;
			tN = tL + model.ta();
			if(Util.debug) System.out.println(model.getName() + " -> Trasition done. NextInternalTransitionIn (" + (tN-tL) + ").");
			counter++;
		}
	}
	
	public void stop() {
		if(myThread!=null) myThread.interrupt();
	}

	////// SUBCLASS
	/**
	 * @author  jlrisco
	 */
	class RTSimulatorTimer extends Thread {
		RTSimulator simulator;
		long timeToSleep;
		
		public RTSimulatorTimer(RTSimulator simulator, long timeToSleep) {
			this.simulator = simulator;
			this.timeToSleep = timeToSleep;
			start();
		}
		
		public void run() {
			try {
				Thread.sleep(timeToSleep);
			}
			catch (Exception ee) {
				//System.out.println("RTSimulatorTimer::run: " + ee.toString());
			}
			simulator.notifyInternalTransition();
		}
	}

}
